import os
import sys
import json
import logging
import pymysql
import requests
import time
from datetime import datetime, date
from decimal import Decimal
import schedule
from datetime import timedelta
from DataExtractor import DataExtractor
from k3cloud_webapi_sdk.main import K3CloudApiSdk
import itertools
import ast


class JinDieSDKImport:

    # 初始化变量
    def __init__(self, config_file='config.properties'):
        self.config_file = config_file
        # 之前取金蝶配置的方式
        self.config = self.load_config()
        # 取金蝶配置 20251002
        self.taskid = 0
        self.kdusername =None
        self.kdpassword =None
        self.kdacctId = None
        self.kdUrl = None
        self.kdappId = None
        self.kdappSecret = None
        self.kdName = None

        self.import_session_id = None

        self.form_id = None  # 金蝶表单ID
        self.table_name = None  # 数据库表名

        self.data_extractor = None
        self.sdk = None  # 金蝶SDK实例
        self.new_set_logging()

        # 批量处理配置
        self.batch_size = 5000
        self.max_retries = 3
        self.normal_wait_time = 5
        self.conflict_wait_time = 30
        self.date_format = "%Y-%m-%d"
        self.duplicate_strategy = 'skip'

        # 初始化金蝶SDK
        # self.initialize_sdk(self.config.get('kingdee.acctID'))

    # 执行主程序
    def run(self):
        try:
            # 1. 获取所有待执行任务（run_flag=12）
            logging.info("获取所有待执行任务...")
            extractor_task = DataExtractor(table_name='', config_file=self.config_file)
            tasks = extractor_task.get_pending_tasks()
            if not tasks:
                logging.info("没有待执行的任务，流程结束")
                return

            # 2. 遍历处理每个任务
            for task in tasks:
                task_id = task['id']
                task_table_name = task['tablename']
                task_starttime = task['starttime']
                task_endtime = task['endtime']
                logging.info(f"\n===== 开始处理任务 ID: {task_id}，表名: {task_table_name} =====")

                # 为当前任务创建独立的导入日志
                task_local_import_log = set()

                try:
                    # 3. 根据表名获取表单配置
                    self._set_form_id_by_tablename(task_table_name)

                    # 4. 初始化当前任务对应的数据提取器
                    self.data_extractor = DataExtractor(
                        table_name=self.table_name,
                        config_file=self.config_file
                    )

                    # 5. 抽取当前任务的数据
                    logging.info("开始抽取数据...")
                    extracted_data = self.data_extractor.extract_data(task)
                    if not extracted_data:
                        logging.info(f"任务 {task_id} 没有待导入的数据")
                        # 无数据时直接更新为22（已同步金蝶）
                        self.data_extractor.update_task_run_flag(task_id, 22)
                        continue

                    # 状态更新：数据转换成功，开始执行操作前改为21（同步金蝶中）
                    self.data_extractor.update_task_run_flag(task_id, 21)

                    # 6. 按单据号分组
                    logging.info("按单据号分组数据...")
                    bill_groups = self.data_extractor.group_data_by_bill_no(extracted_data, task_id)
                    if not bill_groups:
                        logging.info(f"任务 {task_id} 没有有效的单据组")
                        self.data_extractor.update_task_run_flag(task_id, 22)
                        continue

                    # 7. 导入金蝶（处理所有单据）
                    total_bills = len(bill_groups)
                    success_count = 0
                    failed_count = 0

                    for bill_no, bill_data in bill_groups.items():
                        if bill_no in task_local_import_log:
                            logging.info(f"任务 {task_id} - 单据 {bill_no} 已导入，跳过")
                            success_count += 1
                            continue

                        logging.info(f"任务 {task_id} - 开始导入第 {success_count + failed_count + 1}/{total_bills} 个单据：{bill_no}")
                        if task_table_name in('kd_order_detail','kd_order_return','kd_dzmx','kd_thghjb','kd_insurance','kd_coupon'):
                            # 收入数据任务：调用处理数据逻辑（包含追加/替换/更新）
                            result = self.process_bill_data(bill_no, bill_data, task, extracted_data, task_local_import_log)
                        else:
                            # 成本数据任务，都走更新，先导后删
                            result = self.process_cost_bill_data(bill_no, bill_data, task,extracted_data)


                        if result:
                            success_count += 1
                            task_local_import_log.add(bill_no)
                            logging.info(f"任务 {task_id} - 单据 {bill_no} 导入成功（{success_count}/{total_bills}）")
                            # 先导后删，成功导入则对以下表进行对应日期的删除：①销售出库单列表②销售退货单列表③应收单④业务凭证⑤总账凭证
                        else:
                            failed_count += 1
                            logging.error(f"任务 {task_id} - 单据 {bill_no} 导入失败（{failed_count}/{total_bills}）")

                    # 8. 任务所有单据处理完成，更新最终状态
                    logging.info(f"任务 {task_id} 处理结束：成功 {success_count} 个，失败 {failed_count} 个，总计 {total_bills} 个")
                    logging.info("-" * 60)

                    # 状态更新：全部成功→22，有失败→23
                    final_flag = 22 if failed_count == 0 else 23
                    # 移到后面更新
                    #self.data_extractor.update_task_run_flag(task_id, final_flag)

                    # 处理成本数据导入任务成功后的后续删除
                    try:
                        if final_flag == 22 and task_table_name in ('kd_sale_out', 'kd_sale_return'):
                            logging.info(f"\n----------任务：{task_id}为成本数据更新任务，触发后续删除操作----------")
                            starttime = str(task_starttime)
                            endtime = str(task_endtime)
                            # 获取【销售出库单列表】的 FBillNo，做个倒序排列
                            SAL_OUTSTOCK_FBillNo = self.select_OutReturnbillno_by_date(starttime=starttime,endtime=endtime,formid='SAL_OUTSTOCK')
                            SAL_OUTSTOCK_FBillNo.sort(reverse=True)
                            # 遍历查询出来的销售出库单FBillNo，分别查对应的应收单、业务凭证、总账凭证、下游销售退货单及对应的应收单、业务凭证、总账凭证，若有则删除（要反审的先反审）
                            # 构建已删除的销售出库单列表
                            sosDeleted = []
                            for i, sos_BillNo in enumerate(SAL_OUTSTOCK_FBillNo):
                                logging.info(f"开始查询第{i+1}个【销售出库单{sos_BillNo}】是否存在【下游应收单及对应业务凭证和总账凭证】")
                                AR_receivable_FBillNo = self.select_Receivebillno_by_billno(sos_BillNo,formid='AR_receivable',bill_data_type='销售出库单列表')
                                if AR_receivable_FBillNo != []:
                                    BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_Voucher_by_billno(AR_receivable_FBillNo, formid='BAS_BusinessVoucher', bill_data_type='应收单')
                                    if GL_VOUCHER_FGLVoucherID != []:
                                        GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                    # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                    if BAS_BusinessVoucher_FBillNo != []:
                                        BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)
                                    AR_receivable_delresult = self.delete_AR_receivable(formid='AR_receivable',BillNo=AR_receivable_FBillNo)

                                logging.info(f"开始查询第{i+1}个【销售出库单{sos_BillNo}】是否存在【下游业务凭证和总账凭证】")
                                BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_Voucher_by_billno(sos_BillNo, formid='BAS_BusinessVoucher', bill_data_type='销售出库单列表')
                                if GL_VOUCHER_FGLVoucherID != []:
                                    GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                if BAS_BusinessVoucher_FBillNo != []:
                                    BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)

                                logging.info(f"开始查询第{i+1}个【销售出库单{sos_BillNo}】是否存在【下游销售退货单】")
                                SAL_RETURNSTOCK_FBillNo = self.select_SallReturnbillno_by_billno(sos_BillNo,formid='SAL_RETURNSTOCK',bill_data_type='销售出库单列表')
                                if SAL_RETURNSTOCK_FBillNo != []:
                                    # 删除销售出库单前也要删除对应的应收单、总账凭证、业务凭证
                                    for j,srs_BillNo in enumerate(SAL_RETURNSTOCK_FBillNo):
                                        logging.info(f"开始查询【销售退货单{srs_BillNo}】是否存在【下游应收单及对应业务凭证和总账凭证】")
                                        AR_receivable_FBillNo = self.select_Receivebillno_by_billno(srs_BillNo,formid='AR_receivable',bill_data_type='销售退货单列表')
                                        if AR_receivable_FBillNo != []:
                                            BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_Voucher_by_billno(AR_receivable_FBillNo, formid='BAS_BusinessVoucher',bill_data_type='应收单')
                                            if GL_VOUCHER_FGLVoucherID != []:
                                                GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                            # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                            if BAS_BusinessVoucher_FBillNo != []:
                                                BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)
                                            AR_receivable_delresult = self.delete_AR_receivable(formid='AR_receivable',BillNo=AR_receivable_FBillNo)

                                        logging.info(f"开始查询第{i+1}个【销售出库单{sos_BillNo}】的第{j+1}个【销售退货单{srs_BillNo}】是否存在【下游业务凭证和总账凭证】")
                                        BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_BusinessVoucherbillno_by_billno(srs_BillNo, formid='BAS_BusinessVoucher',bill_data_type='销售退货单列表')
                                        if GL_VOUCHER_FGLVoucherID != []:
                                            GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                        # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                        if BAS_BusinessVoucher_FBillNo != []:
                                            BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)

                                        # 确认对应的应收单、总账凭证、业务凭证都已删除(不确认也行，那就看最终的销售退货单删除结果)，进行销售退货单删除
                                        SAL_RETURNSTOCK_delresult = self.delete_SAL_STOCK(formid='SAL_RETURNSTOCK',BillNo=[srs_BillNo])

                                # 确认对应的应收单、总账凭证、业务凭证和下游销售退货都已删除(不确认也行，那就看最终的销售出库单删除结果)，进行销售出库单删除
                                SAL_OUTSTOCK_delresult = self.delete_SAL_STOCK(formid='SAL_OUTSTOCK',BillNo=[sos_BillNo])
                                if SAL_OUTSTOCK_delresult :
                                    sosDeleted.append(sos_BillNo)

                            # 最后进行剩余【销售退货单列表】的删除
                            logging.info(f"开始对剩余的【销售退货】进行删除")
                            # 从待删除里去除已删除的
                            SAL_OUTSTOCK_FBillNo = [x for x in SAL_OUTSTOCK_FBillNo if x not in sosDeleted]
                            if len(SAL_OUTSTOCK_FBillNo) == 0:
                                SAL_RETURNSTOCK_FBillNo = self.select_OutReturnbillno_by_date(starttime=starttime,endtime=endtime,formid='SAL_RETURNSTOCK')
                                logging.info(f"剩余{len(SAL_RETURNSTOCK_FBillNo)}个的【销售退货单】未删除")
                                # 构建已删除的销售退货单列表容器
                                srsDeleted = []
                                if SAL_RETURNSTOCK_FBillNo != []:
                                    for j,srs_BillNo in enumerate(SAL_RETURNSTOCK_FBillNo):
                                        logging.info(f"查询第{j+1}个销售退货单：{srs_BillNo} 对应的【下游应收单及对应业务凭证和总账凭证】")
                                        AR_receivable_FBillNo = self.select_Receivebillno_by_billno(srs_BillNo,formid='AR_receivable',bill_data_type='销售退货单列表')
                                        if AR_receivable_FBillNo != []:
                                            BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_Voucher_by_billno(AR_receivable_FBillNo, formid='BAS_BusinessVoucher',bill_data_type='应收单')
                                            if GL_VOUCHER_FGLVoucherID != []:
                                                GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                            # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                            if BAS_BusinessVoucher_FBillNo != []:
                                                BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)
                                            AR_receivable_delresult = self.delete_AR_receivable(formid='AR_receivable',BillNo=AR_receivable_FBillNo)

                                        logging.info(f"查询第{j+1}个销售退货单：{srs_BillNo} 对应的【下游业务凭证和总账凭证】")
                                        BAS_BusinessVoucher_FBillNo, GL_VOUCHER_FGLVoucherID = self.select_Voucher_by_billno(srs_BillNo, formid='BAS_BusinessVoucher', bill_data_type='销售退货单列表')
                                        if GL_VOUCHER_FGLVoucherID != []:
                                            GL_VOUCHER_delresult = self.delete_GL_VOUCHER(formid='GL_VOUCHER',fid=GL_VOUCHER_FGLVoucherID)
                                        # 若上面的总账凭证被删除了的话这里有可能会返回业务凭证的FBillNo不存在
                                        if BAS_BusinessVoucher_FBillNo != []:
                                            BAS_BusinessVoucher_delresult = self.delete_BAS_BusinessVoucher(formid='BAS_BusinessVoucher', BillNo=BAS_BusinessVoucher_FBillNo)
                                        SAL_RETURNSTOCK_delresult = self.delete_SAL_STOCK(formid='SAL_RETURNSTOCK', BillNo=[srs_BillNo])
                                        if SAL_RETURNSTOCK_delresult :
                                            srsDeleted.append(srs_BillNo)

                                    SAL_RETURNSTOCK_FBillNo = [x for x in SAL_RETURNSTOCK_FBillNo if x not in srsDeleted]
                                    if len(SAL_RETURNSTOCK_FBillNo) == 0:
                                        logging.info(f"【销售出库单】和【销售退货单】均完全删除")
                                        self.data_extractor.update_task_run_flag(task_id, 24)
                                    else:
                                        logging.info(f"【销售出库单】已完全删除，【销售退货单】未完全删除")
                                        self.data_extractor.update_task_run_flag(task_id, 25)
                                else:
                                    logging.info(f"【销售出库单】和【销售退货单】均完全删除")
                                    self.data_extractor.update_task_run_flag(task_id, 24)
                            else:
                                logging.info(f"【销售出库单】未完全删除- 未删除FBillNo共{len(SAL_OUTSTOCK_FBillNo)}个：{SAL_OUTSTOCK_FBillNo} - 【销售退货单】剩余单据不执行删除 ")
                                self.data_extractor.update_task_run_flag(task_id, 25)
                        elif(final_flag != 22 and task_table_name in ('kd_sale_out', 'kd_sale_return')):
                            logging.info(f"导入未成功，不执行删除操作")
                            self.data_extractor.update_task_run_flag(task_id, final_flag)
                        else:
                            logging.info(f"非成本导入，不执行删除操作")
                            self.data_extractor.update_task_run_flag(task_id, final_flag)
                    except Exception as e:
                        logging.error(f"成本导入的后续删除操作执行异常：{e}")
                        self.data_extractor.update_task_run_flag(task_id, 25)


                except Exception as e:
                    # 任务处理过程中发生异常，更新为失败状态23（同步金蝶失败）
                    logging.error(f"任务 {task_id} 处理异常: {str(e)}", exc_info=True)
                    self.data_extractor.update_task_run_flag(task_id, 23)
                    continue  # 继续处理下一个任务

            logging.info("=" * 60)
            logging.info(f"所有任务处理结束，共处理 {len(tasks)} 个任务")
            logging.info("=" * 60)

        except Exception as e:
            logging.error(f"导入流程执行失败: {str(e)}", exc_info=True)

    # 根据数据库表名匹配金蝶表单类型
    def _set_form_id_by_tablename(self, tablename):
        """
        根据数据库表名查询kd_tabless表，获取对应的金蝶表单ID（form_id）
        """
        self.table_name = tablename

        # 连接数据库查询form_id
        conn = None
        cursor = None
        try:
            conn = pymysql.connect(
                host=self.config.get('db.host'),
                port=int(self.config.get('db.port', '3306')),
                user=self.config.get('db.user'),
                password=self.config.get('db.password'),
                db=self.config.get('db.name'),  # 使用业务数据库（或存储kd_tables的数据库）
                charset='utf8mb4'
            )
            cursor = conn.cursor(pymysql.cursors.DictCursor)

            # 查询kd_tables表中对应table_name的form_id
            query_sql = """
                SELECT form_id 
                FROM kd_tables 
                WHERE table_name = %s
            """
            cursor.execute(query_sql, (tablename,))
            result = cursor.fetchone()

            if not result or not result.get('form_id'):
                raise ValueError(f"在kd_tables表中未找到table_name：{tablename}对应的form_id")

            self.form_id = result['form_id']
            logging.info(f"表名{tablename}对应金蝶表单：{self.form_id}")

        except Exception as e:
            logging.error(f"查询kd_tables表获取form_id失败：{str(e)}")
            raise
        finally:
            if cursor:
                cursor.close()
            if conn:
                conn.close()

        # 验证form_id有效性
        if not self.form_id:
            raise ValueError(f"获取到的form_id为空，table_name：{tablename}")

    # 获取任务对应的金蝶账号
    def get_account(self, task_id):

        # 连接数据库查询
        conn = None
        cursor = None
        try:
            conn = pymysql.connect(
                host=self.config.get('db.host'),
                port=int(self.config.get('db.port', '3306')),
                user=self.config.get('db.user'),
                password=self.config.get('db.password'),
                db=self.config.get('db.name'),  # 使用业务数据库（或存储kd_tables的数据库）
                charset='utf8mb4'
            )
            cursor = conn.cursor(pymysql.cursors.DictCursor)

            # 查询
            query_sql = """
                SELECT *
                FROM kd_task
                WHERE id = %s
            """
            cursor.execute(query_sql, (task_id,))
            result = cursor.fetchone()

            if not result or not result.get('remark'):
                raise ValueError(f"在kd_task表中未找到id：{task_id}对应的记录")

            remark = result.get('remark')  # 先用get获取，避免result中无'remark'键时报错
            # 初始化默认值
            id_str = ""
            # 第一步：判断remark是否有效（非None且非空字符串）
            if remark and isinstance(remark, str):
                # 第二步：按逗号分割
                parts = remark.split(',')
                # 第三步：确保分割后至少有2个元素再取值
                if len(parts) >= 1:
                    id_str = parts[0].strip()  # 可选：strip()去除前后空格
            # 最终结果，空值或不符合条件时返回空字符串
            if not isinstance(id_str, str) or not id_str:
                return

            query_sql = """
                            SELECT *
                            FROM kd_account
                            WHERE id = %s
                        """
            cursor.execute(query_sql, (id_str,))
            result = cursor.fetchone()

            if not result or not result.get('name'):
                raise ValueError(f"在kd_account表中未找到id：{id_str}对应的记录")

            self.kdusername = result['username']
            self.kdpassword = result['password']
            self.kdacctId  = result['acctID']
            self.kdUrl = result['url']
            self.kdappId  = result['appID']
            self.kdappSecret = result['appSec']
            self.kdName = result['name']

            logging.info(f"在kd_task表中已找到id：{task_id}对应的记录")

        except Exception as e:
            logging.error(f"查询kd_tables表获取form_id失败：{str(e)}")
            raise
        finally:
            if cursor:
                cursor.close()
            if conn:
                conn.close()

        # 验证账套有效性
        if not self.kdName:
            raise ValueError(f"获取到的金蝶账套为空，TaskId：{task_id}")

    # 配置日志
    def setup_logging(self):
        log_dir = 'logs'
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)

        log_file = os.path.join(log_dir, f'sdk_import_{datetime.now().strftime("%Y%m%d")}.log')

        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file, encoding='utf-8'),
                logging.StreamHandler(sys.stdout)
            ]
        )
        logging.info("=" * 60)
        logging.info("金蝶云星空SDK数据导入脚本启动")
        logging.info("=" * 60)

    def new_set_logging(self):
        log_dir = "logs"
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)

        log_file = os.path.join(log_dir, f'sdk_import_{datetime.now().strftime("%Y%m%d")}.log')

        # 清除现有的root logger handlers，避免重复
        for handler in logging.root.handlers[:]:
            logging.root.removeHandler(handler)

        # 创建formatter
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
        )

        # 文件handler - 记录所有级别（包括DEBUG）
        file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
        file_handler.setLevel(logging.INFO)
        file_handler.setFormatter(formatter)

        # 控制台handler - 只记录INFO及以上
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(logging.INFO)
        console_handler.setFormatter(formatter)

        # 配置root logger
        logging.root.setLevel(logging.DEBUG)
        logging.root.addHandler(file_handler)
        logging.root.addHandler(console_handler)

        # 记录启动信息
        logging.info("=" * 60)
        logging.info("=== 日志系统初始化完成 ===")
        logging.info("金蝶云星空SDK数据导入脚本启动")
        # logging.info(f"日志文件: {os.path.abspath(log_file)}")
        logging.info("=" * 60)

        # 验证调试日志
        # logging.debug("调试日志功能正常")

    # 读取配置文件config.properties，解析参数
    def load_config(self):
        config_path = self.config_file
        if not os.path.exists(config_path):
            raise FileNotFoundError("配置文件未找到")

        config = {}
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#') and '=' in line:
                        key, value = line.split('=', 1)
                        config[key.strip()] = value.strip()
        except Exception as e:
            logging.error(f"加载配置文件失败: {e}")
            raise
        return config

    # 标记单据为 “已导入”，避免重复处理
    def mark_bill_imported(self, bill_no, import_log):
        import_log.add(bill_no)
        logging.debug(f"标记单据 {bill_no} 已导入，当前任务已导入单据数: {len(import_log)}")

    # 构建金蝶单据的导入数据模型
    def create_bill_model(self, bill_data, task_id):
        if not bill_data:
            raise ValueError("单据数据不能为空")

        bill_no = bill_data[0].get("FBillNo", "")
        return self.data_extractor.transform_data(bill_data, bill_no, task_id)

    # 构建 "追加数据" 的数据模型
    def create_append_model(self, bill_id, batch_data):
        if not batch_data:
            raise ValueError("批次数据不能为空")
        if not bill_id:
            raise ValueError("追加更新操作必须提供单据ID（FID）")

        bill_no = batch_data[0].get("FBillNo", "")
        return self.append_bill_entries(bill_id, bill_no, 0, batch_data)

    # 生成金蝶单据数据（追加数据）的请求体
    def append_bill_entries(self, bill_id, bill_no, offset, details):
        try:
            if getattr(self, 'table_name', '') == "kd_ship_fee":
                update_data = {
                    "NeedUpDateFields": [],
                    "NeedReturnFields": [],
                    "IsDeleteEntry": "false",
                    "SubSystemId": "",
                    "IsVerifyBaseDataField": "false",
                    "IsEntryBatchFill": "true",
                    "ValidateFlag": "false",
                    "IsAutoSubmitAndAudit": False,
                    "NumberSearch": "true",
                    "IsAutoAdjustField": "false",
                    "InterationFlags": "",
                    "IgnoreInterationFlag": "",
                    "IsControlPrecision": "false",
                    "ValidateRepeatJson": "false",
                    "Model": {
                        "FID": bill_id,
                        "FBillNo": bill_no,
                        "FMultiEntryFlag": "true",
                        "FEntityDetail": []
                    }
                }
            else:
                update_data = {
                    "NeedUpDateFields": [],
                    "NeedReturnFields": [],
                    "IsDeleteEntry": "false",
                    "SubSystemId": "",
                    "IsVerifyBaseDataField": "false",
                    "IsEntryBatchFill": "true",
                    "ValidateFlag": "false",
                    "IsAutoSubmitAndAudit": False,
                    "NumberSearch": "true",
                    "IsAutoAdjustField": "false",
                    "InterationFlags": "",
                    "IgnoreInterationFlag": "",
                    "IsControlPrecision": "false",
                    "ValidateRepeatJson": "false",
                    "Model": {
                        "FID": bill_id,
                        "FBillNo": bill_no,
                        "FMultiEntryFlag": "true",
                        "FEntity": []
                    }
                }

            for index, row in enumerate(details):
                entity_item = {"FSeq": offset + index + 1}
                self.data_extractor._new_map_entity_fields_api_style(entity_item, row)
                if getattr(self, 'table_name', '') == "kd_ship_fee":
                    update_data["Model"]["FEntityDetail"].append(entity_item)
                else:
                    update_data["Model"]["FEntity"].append(entity_item)

            return update_data

        except Exception as e:
            logging.error(f"创建追加数据时出错: {str(e)}")
            raise

    # 检查金蝶系统中该表是否已存在指定单据编号的单据（追加用）
    def check_bill_exists_by_no(self, bill_no):
        if not bill_no:
            logging.error("检查单据存在性失败：单据号为空")
            return False
        try:
            # 1. 正确构建过滤条件
            filter_str = f"FBillNo='{bill_no}'"
            query_params = {
                "FormId": self.form_id,
                "FieldKeys": "FBillNo",
                "FilterString": filter_str,
                "OrderBy": "",
                "Top": 1
            }

            logging.info(f"检查单据是否存在：表单ID={self.form_id}，单据号={bill_no}，Filter={filter_str}")

            response = self.sdk.ExecuteBillQuery(query_params)
            if not response:
                logging.warning(f"检查单据[{bill_no}]存在性无响应，默认视为不存在")
                return False

            # 解析响应数据
            resp_data = json.loads(response)
            exists = False

            # 2. 处理列表类型响应
            if isinstance(resp_data, list):
                # 遍历列表，检查是否有与目标单据号一致的记录
                for item in resp_data:
                    if isinstance(item, list) and len(item) > 0 and item[0] == bill_no:
                        exists = True
                        break

            # 3. 处理字典类型响应
            elif isinstance(resp_data, dict):
                result = resp_data.get("Result", {})
                if result.get("ResponseStatus", {}).get("IsSuccess", False):
                    data = result.get("Data", [])
                    for item in data:
                        # 提取单据号并比对
                        bill_no_in_response = item.get("FBillNo") if isinstance(item, dict) else (item[0] if isinstance(item, list) and len(item) > 0 else None)
                        if bill_no_in_response == bill_no:
                            exists = True
                            break
                else:
                    error_msg = result.get("ResponseStatus", {}).get("Errors", [{}])[0].get("Message", "查询失败")
                    logging.warning(f"检查单据[{bill_no}]存在性失败：{error_msg}，默认视为不存在")
                    return False

            logging.info(f"单据[{bill_no}]存在性检查结果：{'已存在' if exists else '不存在'}")
            return exists

        except json.JSONDecodeError:
            logging.error(f"检查单据[{bill_no}]存在性失败：响应数据不是有效的JSON")
            return False
        except Exception as e:
            logging.error(f"检查单据[{bill_no}]存在性发生异常：{str(e)}", exc_info=True)
            return False

    # 删除指定单据编号数据（替換用）
    def delete_bill_by_no(self, bill_no):
        if not bill_no:
            logging.error("删除失败：单据号为空")
            return False, "单据号不能为空"

        try:
            delete_data = {
                "CreateOrgId": self.config.get("kingdee.orgId", 0),
                "Numbers": [bill_no],
                "Ids": "",
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            # 转换CreateOrgId为整数
            try:
                delete_data["CreateOrgId"] = int(delete_data["CreateOrgId"])
            except (ValueError, TypeError):
                delete_data["CreateOrgId"] = 0
                logging.warning(f"组织内码配置无效，默认使用 0")

            logging.info(f"执行删除：表单ID={self.form_id}，单据号={bill_no}，参数={json.dumps(delete_data)}")
            response = self.sdk.Delete(self.form_id, delete_data)
            if not response:
                logging.warning(f"删除单据 {bill_no}：接口无响应，将继续执行后续导入")
                return True, "删除接口无响应，继续导入"

            resp_json = json.loads(response)
            result = resp_json.get("Result", {})
            response_status = result.get("ResponseStatus", {})
            is_success = response_status.get("IsSuccess", False)
            msg_code = response_status.get("MsgCode", "0")
            error_msg = ""

            if not is_success:
                errors = response_status.get("Errors", [])
                error_msg = errors[0].get("Message", "删除失败") if errors else "未知错误"
                logging.warning(f"删除单据 {bill_no} 接口返回失败：MsgCode={msg_code}，原因={error_msg}，将继续执行后续导入")
                return True, f"删除接口失败（{msg_code}）：{error_msg}，继续导入"

            logging.info(f"删除单据 {bill_no} 接口返回成功，将继续执行后续导入")
            return True, "删除接口返回成功，继续导入"

        except Exception as e:
            err_msg = f"删除异常：{str(e)}"
            logging.error(f"单据 {bill_no} {err_msg}，将继续执行后续导入")
            return True, f"删除异常：{err_msg}，继续导入"  # 捕获异常后仍不终止

    # 根据单据号bill_no查询金蝶系统中对应的单据 ID(FID)（更新用）
    def query_bill_id_by_no(self, bill_no):
        if not bill_no:
            logging.error("查询FID失败：单据号为空")
            return []

        try:
            # 1. 构建过滤条件
            escaped_bill_no = bill_no.replace("'", "''")
            filter_str = f"FBillNo='{escaped_bill_no}'"

            logging.info(f"查询FID：表单ID={self.form_id}，过滤条件={filter_str}，目标bill_no={bill_no}")

            query_data = {
                "FormId": self.form_id,
                "FieldKeys": "FID,FBillNo",
                "Filter": filter_str,
                "OrderBy": "FCreateTime DESC",
                "Top": 10
            }

            response = self.sdk.ExecuteBillQuery(query_data)
            if not response:
                logging.error(f"查询单据 {bill_no} 的FID失败：接口无响应")
                return []

            resp_json = json.loads(response)
            fid_list = []
            target_bill_no = bill_no.strip()

            # 2.遍历响应中的每一组[FID, FBillNo]，精准匹配目标bill_no
            if isinstance(resp_json, list):
                logging.debug(f"解析列表响应，共{len(resp_json)}组[FID, FBillNo]数据")
                for item in resp_json:
                    # 校验数据格式：必须是包含FID和FBillNo的列表
                    if not isinstance(item, list) or len(item) < 2:
                        continue

                    # 提取当前组的FID和FBillNo（按FieldKeys顺序：FID在第0位，FBillNo在第1位）
                    current_fid = item[0]
                    current_fbillno = str(item[1]).strip()

                    # 仅保留FBillNo与目标bill_no完全一致的FID
                    if current_fbillno == target_bill_no:
                        current_fid_str = str(current_fid)
                        fid_list.append(current_fid_str)
                        logging.info(f"找到匹配项：FBillNo={current_fbillno} → FID={current_fid_str}")

            elif isinstance(resp_json, dict):
                response_status = resp_json.get("Result", {}).get("ResponseStatus", {})
                if not response_status.get("IsSuccess", False):
                    error_msg = response_status.get("Errors", [{}])[0].get("Message", "未知错误")
                    logging.error(f"查询FID失败：{error_msg}")
                    return []

                data_list = resp_json.get("Result", {}).get("Data", [])
                logging.debug(f"解析字典响应，共{len(data_list)}组数据")
                for item in data_list:
                    if isinstance(item, dict):
                        current_fid = item.get("FID")
                        current_fbillno = str(item.get("FBillNo", "")).strip()
                    elif isinstance(item, list) and len(item) >= 2:
                        current_fid = item[0]
                        current_fbillno = str(item[1]).strip()
                    else:
                        logging.warning(f"无效数据格式：{item}，跳过")
                        continue

                    if current_fbillno == target_bill_no and current_fid is not None:
                        current_fid_str = str(current_fid)
                        fid_list.append(current_fid_str)
                        logging.info(f"找到匹配项：FBillNo={current_fbillno} → FID={current_fid_str}")

            # 4. 输出结果
            if not fid_list:
                logging.info(f"未找到 bill_no={bill_no} 对应的FID")
            else:
                logging.info(f"bill_no={bill_no} 匹配到 {len(fid_list)} 个FID：{fid_list}")
            return fid_list

        except Exception as e:
            logging.error(f"查询FID异常（bill_no={bill_no}）：{str(e)}", exc_info=True)
            return []

    # 构建 "更新数据" 的数据模型
    def create_update_model(self, bill_id, bill_data, is_delete_entry=True):
        if not bill_data:
            raise ValueError("更新数据不能为空")
        if not bill_id:
            raise ValueError("更新操作必须提供单据ID（FID）")

        # 从分组后的 bill_data 中取 FBillNo（所有记录的 FBillNo 应一致）
        bill_no1 = bill_data[0].get("FBillNo", "")
        if not bill_no1:
            raise ValueError("更新数据中无有效 FBillNo")

        # 验证所有记录的 FBillNo 是否一致（确保数据正确性）
        all_bill_nos = {item.get("FBillNo", "") for item in bill_data}
        if len(all_bill_nos) > 1:
            logging.warning(f"更新数据中 FBillNo 不一致，取第一个：{bill_no1}")

        # 调用生成请求体的函数时，传入is_delete_entry参数
        return self.update_bill_entries(bill_id, bill_no1, bill_data, is_delete_entry)

    # 生成金蝶单据数据（更新数据）的请求体
    def update_bill_entries(self, bill_id, bill_no1, details, is_delete_entry=True):
        try:
            replace_data = {
                "NeedUpDateFields": [],
                "NeedReturnFields": [],
                "IsDeleteEntry": is_delete_entry,  # 动态控制是否删除原有分录
                "SubSystemId": "",
                "IsVerifyBaseDataField": "false",
                "IsEntryBatchFill": "true",
                "ValidateFlag": "false",
                "IsAutoSubmitAndAudit": False,
                "NumberSearch": "true",
                "IsAutoAdjustField": "false",
                "InterationFlags": "",
                "IgnoreInterationFlag": "",
                "IsControlPrecision": "false",
                "ValidateRepeatJson": "false",
                "Model": {
                    "FID": bill_id,  # 必须提供FID用于定位单据
                    "FBillNo": bill_no1,
                    "FMultiEntryFlag": "true",
                    "FEntity": []
                }
            }

            # 计算当前批次的FSeq偏移量（避免与前序批次重复）
            # 偏移量 = 已导入的总行数（前序批次累计行数）
            offset = self._get_existing_entries_count(bill_id) if not is_delete_entry else 0

            for index, row in enumerate(details):
                # FSeq = 偏移量 + 当前批次内的索引 + 1（确保序号连续）
                entity_item = {"FSeq": offset + index + 1}
                self.data_extractor._new_map_entity_fields_api_style(entity_item, row)
                replace_data["Model"]["FEntity"].append(entity_item)

            return replace_data
        except Exception as e:
            logging.error(f"创建更新数据时出错: {str(e)}")
            raise

    # 查询指定单据（FID）已有的明细行数，用于后续批次FSeq偏移计算）（更新用）
    def _get_existing_entries_count(self, bill_id):
        if not bill_id:
            logging.error("查询明细行数失败：单据ID（FID）为空")
            return 0

        try:
            # 构造查询条件：按FID定位单据，查询明细行的FSeq（最大FSeq即总行数）
            query_data = {
                "FormId": self.form_id,
                "FieldKeys": "FEntity.FSeq",  # 查询明细行的序号字段
                "Filter": f"FID='{bill_id}'",  # 按FID过滤单据
                "OrderBy": "FEntity.FSeq DESC",  # 按序号倒序，取第一条即最大序号
                "Top": 1  # 仅取最大的FSeq（减少数据传输）
            }

            response = self.sdk.ExecuteBillQuery(query_data)
            if not response:
                logging.warning(f"查询单据 {bill_id} 明细行数失败：无响应")
                return 0

            resp_json = json.loads(response)
            max_seq = 0  # 默认明细行数为0

            # 1. 处理字典型响应（包含Result和ResponseStatus）
            if isinstance(resp_json, dict):
                if resp_json.get("Result", {}).get("ResponseStatus", {}).get("IsSuccess"):
                    data = resp_json.get("Result", {}).get("Data", [])
                    if data and isinstance(data[0], dict):
                        # 从FEntity字典中提取FSeq
                        max_seq = data[0].get("FEntity", {}).get("FSeq", 0)
                    elif data and isinstance(data[0], list) and len(data[0]) > 0:
                        # 列表嵌套格式：取第一个元素（FSeq）
                        max_seq = data[0][0]
                else:
                    error_msg = resp_json.get("Result", {}).get("ResponseStatus", {}).get("Errors", [{}])[0].get(
                        "Message", "未知错误")
                    logging.warning(f"查询单据 {bill_id} 明细行数失败：{error_msg}")
                    return 0

            # 2. 处理列表型响应（直接返回明细数据）
            elif isinstance(resp_json, list):
                if resp_json:  # 确保列表非空
                    first_item = resp_json[0]
                    if isinstance(first_item, dict):
                        f_entity = first_item.get("FEntity", {})
                        max_seq = f_entity.get("FSeq", 0)  # 精准提取FSeq值
                    elif isinstance(first_item, list) and len(first_item) > 0:
                        # 若明细行是列表格式（如[5000]），直接取FSeq
                        max_seq = first_item[0]
                else:
                    logging.warning(f"查询单据 {bill_id} 明细行数失败：响应列表为空")
                    return 0

            # 3. 处理未知类型响应
            else:
                logging.warning(f"查询单据 {bill_id} 明细行数失败：响应类型未知（{type(resp_json)}）")
                return 0

            # 转换为整数
            try:
                max_seq = int(str(max_seq)) if max_seq is not None else 0
            except (ValueError, TypeError):
                logging.warning(f"单据 {bill_id} 的FSeq格式异常（{max_seq}），默认按0处理")
                max_seq = 0

            logging.info(f"单据 {bill_id} 已存在 {max_seq} 条明细行")
            return max_seq

        except Exception as e:
            logging.error(f"查询单据 {bill_id} 明细行数异常: {str(e)}", exc_info=True)
            return 0

    # 初始化金蝶云星空 SDK，建立连接并登录
    def initialize_sdk(self, acct_id):
        try:
            username = self.config.get('kingdee.username')
            password = self.config.get('kingdee.password')
            server_url = self.config.get('kingdee.url')

            if not username or not password:
                raise ValueError(f"未找到金蝶用户名或密码配置")

            if not server_url:
                raise ValueError(f"未找到金蝶服务器地址配置")

            self.sdk = K3CloudApiSdk(server_url)

            login_success = False
            login_methods = [
                {'username': username, 'password': password},
                {'username': f'{username}@langqi', 'password': password},
                {'username': f'{username}@{acct_id}', 'password': password}
            ]

            app_id = self.config.get('kingdee.appID')
            app_sec = self.config.get('kingdee.appSec')

            if not app_id or not app_sec:
                raise ValueError(f"未找到金蝶AppID或AppSec配置")

            self.sdk.InitConfig(acct_id, username, app_id, app_sec, server_url, 2052)

            for method in login_methods:
                try:
                    result = self.sdk.Login(method['username'], method['password'])
                    if result:
                        login_success = True
                        logging.info(f"登录成功: {method['username']}")
                        break
                    else:
                        logging.warning(f"登录失败: {method['username']}")
                except Exception as e:
                    logging.warning(f"登录方法失败: {method['username']} - {str(e)}")
                    continue

            if not login_success:
                raise Exception("所有登录方法都失败了")

        except Exception as e:
            logging.error(f"SDK初始化失败: {str(e)}")
            raise

    # 检查 SDK 状态
    def ensure_sdk(self):
        if self.sdk:
            return True
        try:
            logging.warning("检测到SDK未初始化，尝试重新初始化...")
            self.initialize_sdk(self.config.get('kingdee.acctID'))
            return self.sdk is not None
        except Exception as e:
            logging.error(f"SDK重新初始化失败: {str(e)}")
            return False

    # 初始化金蝶云星空 SDK，建立连接并登录【新】
    def new_initialize_sdk(self):
        try:
            username = self.kdusername
            password = self.kdpassword
            server_url = self.kdUrl
            acctId = self.kdacctId

            if not username or not password:
                raise ValueError(f"未找到金蝶用户名或密码配置")

            if not server_url:
                raise ValueError(f"未找到金蝶服务器地址配置")

            self.sdk = K3CloudApiSdk(server_url)

            login_success = False
            login_methods = [
                {'username': username, 'password': password},
                {'username': f'{username}@langqi', 'password': password},
                {'username': f'{username}@{acctId}', 'password': password}
            ]

            app_id = self.kdappId
            app_sec = self.kdappSecret

            if not app_id or not app_sec:
                raise ValueError(f"未找到金蝶AppID或AppSec配置")

            self.sdk.InitConfig(acctId, username, app_id, app_sec, server_url, 2052)

            for method in login_methods:
                try:
                    result = self.sdk.Login(method['username'], method['password'])
                    if result:
                        login_success = True
                        logging.info(f"登录成功: {method['username']}")
                        break
                    else:
                        logging.warning(f"登录失败: {method['username']}")
                except Exception as e:
                    logging.warning(f"登录方法失败: {method['username']} - {str(e)}")
                    continue

            if not login_success:
                raise Exception("所有登录方法都失败了")

        except Exception as e:
            logging.error(f"SDK初始化失败: {str(e)}")
            raise

    # 检查 SDK 状态【新】
    def new_ensure_sdk(self):
        if self.sdk:
            return True
        if self.taskid == 0:
            return False
        try:
            logging.warning("检测到SDK未初始化，尝试重新初始化...")
            self.get_account(self.taskid)
            self.new_initialize_sdk()
            return self.sdk is not None
        except Exception as e:
            logging.error(f"SDK重新初始化失败: {str(e)}")
            return False

    # 单批/多批 处理各个操作类型的单据数据
    def process_bill_data(self, bill_no, bill_data, task, extracted_data, import_log):
        """
        task: 包含id和operate_type的任务字典
        import_log: 任务级的导入日志集合（从run方法传递过来）
        """
        try:
            task_id = task['id']
            operate_type = task.get('operate_type', 1)  # 1-追加 2-替换 3-更新
            op_type_desc = {1: "追加", 2: "替换", 3: "更新"}.get(operate_type, "未知")

            logging.info(f"开始{op_type_desc}处理单据 {bill_no}，共 {len(bill_data)} 条明细行，任务ID: {task_id}")
            self.taskid = task_id
            batch_size = self.batch_size  # 使用类中定义的批量大小
            # if not self.ensure_sdk():
            if not self.new_ensure_sdk():
                logging.error(f"任务 {task_id} - SDK未初始化或登录失败，无法处理单据")
                return False

            # 处理追加操作
            if operate_type == 1:
                # 检查金蝶系统中是否已存在该单据
                bill_exists = self.check_bill_exists_by_no(bill_no)
                if bill_exists:
                    logging.info(f"任务 {task_id} - 单据 {bill_no} 已在金蝶系统中存在，跳过追加操作")
                    # 计数但不标记为已导入
                    return True
                # 单批处理（数据量小于等于批次大小）
                if len(bill_data) <= batch_size:
                    logging.info(f"任务 {task_id} - 单批{op_type_desc}单据 {bill_no}，共 {len(bill_data)} 条明细行")
                    try:
                        # 创建单据模型
                        bill_model = self.create_bill_model(bill_data, task_id)
                        # 调用SDK保存
                        response = self.sdk.Save(self.form_id, bill_model)
                        resp_json = json.loads(response) if response else None

                        if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                            bill_id = resp_json['Result']['Id']
                            logging.info(f"任务 {task_id} - 单据{op_type_desc}成功，ID: {bill_id}")
                            self.mark_bill_imported(bill_no, import_log)
                            return True
                        else:
                            error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                0].get('Message', '未知错误')
                            logging.error(f"任务 {task_id} - 单据{op_type_desc}失败: {error_msg}")
                            return False
                    except Exception as e:
                        logging.error(f"任务 {task_id} - 单批{op_type_desc}异常: {str(e)}")
                        return False

                # 多批处理（数据量大于批次大小）
                else:
                    # 第1批数据创建主单据FID
                    first_batch = bill_data[:batch_size]
                    remaining_data = bill_data[batch_size:]
                    logging.info(f"任务 {task_id} - 第1批{op_type_desc}单据 {bill_no}，共 {len(first_batch)} 条明细行")

                    try:
                        # 创建第1批数据模型
                        bill_model = self.create_bill_model(first_batch, task_id)
                        response = self.sdk.Save(self.form_id, bill_model)
                        resp_json = json.loads(response) if response else None

                        if not (resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess')):
                            error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                0].get('Message', '未知错误')
                            logging.error(f"任务 {task_id} - 第1批{op_type_desc}失败: {error_msg}")
                            return False

                        bill_id = resp_json['Result']['Id']
                        logging.info(f"任务 {task_id} - 第1批{op_type_desc}成功，单据ID: {bill_id}")

                    except Exception as e:
                        logging.error(f"任务 {task_id} - 第1批{op_type_desc}异常: {str(e)}")
                        return False

                    # 处理剩余批次
                    remaining_batches = (len(remaining_data) + batch_size - 1) // batch_size
                    logging.info(f"任务 {task_id} - 剩余 {len(remaining_data)} 条明细，将分 {remaining_batches} 批处理")

                    success_batches = 1
                    failed_batches = 0

                    for batch_num in range(remaining_batches):
                        start_idx = batch_num * batch_size
                        end_idx = min(start_idx + batch_size, len(remaining_data))
                        batch_data = remaining_data[start_idx:end_idx]
                        current_batch_num = batch_num + 2  # 从第2批开始计数

                        logging.info(f"任务 {task_id} - 第 {current_batch_num} 批{op_type_desc}，共 {len(batch_data)} 条明细")

                        try:
                            # 创建追加数据模型（使用第1批生成的bill_id）
                            append_model = self.create_append_model(bill_id, batch_data)
                            response = self.sdk.Save(self.form_id, append_model)
                            resp_json = json.loads(response) if response else None

                            if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                                logging.info(f"任务 {task_id} - 第 {current_batch_num} 批{op_type_desc}成功")
                                success_batches += 1
                            else:
                                error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                    0].get('Message', '未知错误')
                                logging.error(f"任务 {task_id} - 第 {current_batch_num} 批{op_type_desc}失败: {error_msg}")
                                failed_batches += 1

                        except Exception as e:
                            logging.error(f"任务 {task_id} - 第 {current_batch_num} 批{op_type_desc}异常: {str(e)}")
                            failed_batches += 1

                        # 批次间等待
                        time.sleep(self.normal_wait_time)

                    # 汇总多批处理结果
                    logging.info(f"任务 {task_id} - 所有批次处理完成，成功: {success_batches} 批，失败: {failed_batches} 批")
                    if failed_batches == 0:
                        self.mark_bill_imported(bill_no, import_log)
                        logging.info(f"任务 {task_id} - 单据 {bill_no} 全部批次{op_type_desc}成功")
                        return True
                    else:
                        logging.error(f"任务 {task_id} - 单据 {bill_no} 存在失败批次")
                        return False

            # 处理替换操作
            elif operate_type == 2:
                # 1. 执行删除
                delete_success, delete_msg = self.delete_bill_by_no(bill_no)
                logging.info(f"任务 {task_id} - 单据 {bill_no} 删除阶段完成，结果：{delete_msg}")
                logging.info(f"任务 {task_id} - 开始导入替换后的新数据")
                # 单批处理（数据量小于等于批次大小）
                if len(bill_data) <= batch_size:
                    logging.info(f"任务 {task_id} - 单批导入替换数据 {bill_no}，共 {len(bill_data)} 条明细行")
                    try:
                        bill_model = self.create_bill_model(bill_data, task_id)
                        response = self.sdk.Save(self.form_id, bill_model)
                        resp_json = json.loads(response) if response else None

                        if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                            bill_id = resp_json['Result']['Id']
                            logging.info(f"任务 {task_id} - 替换数据导入成功，单据ID: {bill_id}")
                            self.mark_bill_imported(bill_no, import_log)
                            return True
                        else:
                            error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                0].get('Message', '未知错误')
                            logging.error(f"任务 {task_id} - 替换数据导入失败: {error_msg}")
                            return False
                    except Exception as e:
                        logging.error(f"任务 {task_id} - 单批替换数据导入异常: {str(e)}")
                        return False

                # 多批处理（数据量大于批次大小）
                else:
                    first_batch = bill_data[:batch_size]
                    remaining_data = bill_data[batch_size:]
                    logging.info(f"任务 {task_id} - 第1批替换数据 {bill_no}，共 {len(first_batch)} 条明细行")

                    try:
                        # 第1批创建主单据
                        bill_model = self.create_bill_model(first_batch, task_id)
                        response = self.sdk.Save(self.form_id, bill_model)
                        resp_json = json.loads(response) if response else None

                        if not (resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess')):
                            error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                0].get('Message', '未知错误')
                            logging.error(f"任务 {task_id} - 第1批替换数据导入失败: {error_msg}")
                            return False

                        bill_id = resp_json['Result']['Id']
                        logging.info(f"任务 {task_id} - 第1批替换数据导入成功，单据ID: {bill_id}")

                    except Exception as e:
                        logging.error(f"任务 {task_id} - 第1批替换数据导入异常: {str(e)}")
                        return False

                    remaining_batches = (len(remaining_data) + batch_size - 1) // batch_size
                    logging.info(f"任务 {task_id} - 剩余 {len(remaining_data)} 条替换明细，分 {remaining_batches} 批处理")

                    success_batches = 1
                    failed_batches = 0

                    for batch_num in range(remaining_batches):
                        start_idx = batch_num * batch_size
                        end_idx = min(start_idx + batch_size, len(remaining_data))
                        batch_data = remaining_data[start_idx:end_idx]
                        current_batch_num = batch_num + 2

                        logging.info(f"任务 {task_id} - 第 {current_batch_num} 批替换数据，共 {len(batch_data)} 条明细")

                        try:
                            append_model = self.create_append_model(bill_id, batch_data)
                            response = self.sdk.Save(self.form_id, append_model)
                            resp_json = json.loads(response) if response else None

                            if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                                logging.info(f"任务 {task_id} - 第 {current_batch_num} 批替换数据导入成功")
                                success_batches += 1
                            else:
                                error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                    0].get('Message', '未知错误')
                                logging.error(f"任务 {task_id} - 第 {current_batch_num} 批替换数据导入失败: {error_msg}")
                                failed_batches += 1

                        except Exception as e:
                            logging.error(f"任务 {task_id} - 第 {current_batch_num} 批替换数据导入异常: {str(e)}")
                            failed_batches += 1

                        time.sleep(self.normal_wait_time)

                    # 汇总替换结果
                    logging.info(f"任务 {task_id} - 替换数据所有批次处理完成，成功: {success_batches} 批，失败: {failed_batches} 批")
                    if failed_batches == 0:
                        self.mark_bill_imported(bill_no, import_log)
                        logging.info(f"任务 {task_id} - 单据 {bill_no} 替换成功（删除+重新导入完成）")
                        return True
                    else:
                        logging.error(f"任务 {task_id} - 单据 {bill_no} 替换失败：存在导入失败批次")
                        return False

            # 处理更新操作
            elif operate_type == 3:
                # 查询单据对应的FID
                bill_ids = self.query_bill_id_by_no(bill_no)
                if not bill_ids:
                    logging.error(f"任务 {task_id} - 更新失败：未找到 bill_no={bill_no} 对应的FID")
                    return False

                # 2. 直接取第一个FID
                target_bill_id = bill_ids[0]

                # 3. 后续更新逻辑不变
                total_rows = len(bill_data)
                batch_size = self.batch_size
                total_batches = (total_rows + batch_size - 1) // batch_size
                logging.info(
                    f"任务 {task_id} - 更新单据：FBillNo={bill_no}，目标FID={target_bill_id}，总明细={total_rows}条，分{total_batches}批处理"
                )

                success_batches = 0
                failed_batches = 0

                for batch_num in range(total_batches):
                    start_idx = batch_num * batch_size
                    end_idx = min(start_idx + batch_size, total_rows)
                    batch_data = bill_data[start_idx:end_idx]
                    current_batch_num = batch_num + 1

                    logging.info(f"任务 {task_id} - 第 {current_batch_num} 批更新，处理 {len(batch_data)} 条明细")

                    try:
                        # 动态控制删除开关：第1批删原有数据，后续批不删（否则IsDeleteEntry= Ture 会把已经更新的第1批数据再次删除）
                        is_delete_entry = (current_batch_num == 1)
                        # 传入删除开关
                        replace_model = self.create_update_model(
                            bill_id=target_bill_id,
                            bill_data=batch_data,
                            is_delete_entry=is_delete_entry  # 按批次控制删除
                        )

                        # 调用SDK保存
                        response = self.sdk.Save(self.form_id, replace_model)
                        resp_json = json.loads(response) if response else None

                        if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                            logging.info(f"任务 {task_id} - 第 {current_batch_num} 批更新成功")
                            success_batches += 1
                        else:
                            error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[
                                0].get('Message', '未知错误')
                            logging.error(f"任务 {task_id} - 第 {current_batch_num} 批更新失败: {error_msg}，响应：{response}")
                            failed_batches += 1

                    except Exception as e:
                        logging.error(f"任务 {task_id} - 第 {current_batch_num} 批更新异常: {str(e)}", exc_info=True)
                        failed_batches += 1

                    # 批次间等待
                    time.sleep(self.normal_wait_time)

                # 汇总多批更新结果
                logging.info(f"任务 {task_id} - 所有更新批次处理完成，成功: {success_batches} 批，失败: {failed_batches} 批")
                if failed_batches == 0:
                    self.mark_bill_imported(bill_no, import_log)
                    logging.info(f"任务 {task_id} - 单据 {bill_no} 全部更新成功（总明细：{total_rows}条）")
                    return True
                else:
                    logging.error(f"任务 {task_id} - 单据 {bill_no} 存在更新失败批次，需人工检查")
                    return False

            # 处理未知操作类型
            else:
                logging.error(f"任务 {task_id} - 不支持的操作类型: {operate_type}")
                return False

        except Exception as e:
            logging.error(f"处理单据 {bill_no} 时出错: {str(e)}", exc_info=True)
            return False

    # 成本数据（销售出库中间表和销售退货中间表）的更新
    def process_cost_bill_data(self, bill_no,bill_data,task, extracted_data):
        task_id = task['id']
        # 成本中间表这两个都是用更新的，应该在前端就限制好，目前不管选哪个导入规则这边的处理逻辑都是更新
        operate_type = task.get('operate_type', 1)  # 1-追加 2-替换 3-更新
        op_type_desc = {1: "追加", 2: "替换", 3: "更新"}.get(operate_type, "未知")
        self.taskid = task_id
        batch_size = self.batch_size
        if not self.new_ensure_sdk():
            logging.error(f"任务 {task_id} - SDK未初始化或登录失败，无法处理单据")
            return False

        # 单批处理
        if len(bill_data) <= batch_size:
            logging.info(f"开始{op_type_desc}处理单据 {bill_no}，共 {len(bill_data)} 条明细行，进行单批处理，任务ID: {task_id}")
            try:
                # 创建单据模型
                bill_model = self.create_bill_model(bill_data, task_id)
                # 调用SDK保存
                response = self.sdk.Save(self.form_id, bill_model)
                resp_json = json.loads(response) if response else None
                if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                    bill_id = resp_json['Result']['Id']
                    logging.info(f"任务 {task_id} - 单据{op_type_desc}成功，ID: {bill_id}")
                    # self.mark_bill_imported(bill_no, import_log)
                    return True
                else:
                    error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[0].get('Message', '未知错误')
                    logging.error(f"任务 {task_id} - 单据{op_type_desc}失败: {error_msg}")
                    return False
            except Exception as e:
                logging.error(f"任务 {task_id} - 单批{op_type_desc}异常: {str(e)}")
                return False
        # 多批次处理
        else:
            success_batches = 0
            failed_batches = 0
            logging.info(f"开始{op_type_desc}处理单据 {bill_no} - 共 {len(bill_data)} 条明细行 - 进行多批处理，每批{batch_size}数据量 - 任务ID: {task_id}")
            for i in range(0,len(bill_data),batch_size):
                try:
                    bill_data = bill_data[i:i + batch_size]
                    logging.info(f"开始{op_type_desc}处理单据 {bill_no} 的第{i//batch_size + 1}批数据，数据量{len(bill_data)} - 任务ID: {task_id}")
                    # 创建单据模型
                    bill_model = self.create_bill_model(bill_data, task_id)
                    # 调用SDK保存
                    response = self.sdk.Save(self.form_id, bill_model)
                    resp_json = json.loads(response) if response else None
                    if resp_json and resp_json.get('Result', {}).get('ResponseStatus', {}).get('IsSuccess'):
                        success_batches += 1
                        logging.info(f"任务 {task_id} -单据 {bill_no}- 第{i//batch_size + 1}批{op_type_desc}成功")
                    else:
                        failed_batches += 1
                        error_msg = resp_json.get('Result', {}).get('ResponseStatus', {}).get('Errors', [{}])[0].get('Message', '未知错误')
                        logging.error(f"任务 {task_id} - 单据 {bill_no}- 第{i//batch_size + 1}批{op_type_desc}失败: {error_msg}")
                    # 批次间等待
                    time.sleep(self.normal_wait_time)
                except Exception as e:
                        logging.error(f"任务 {task_id} - 单据 {bill_no}- 多批{op_type_desc}异常: {str(e)}")
                        return False

            # 汇总多批处理结果
            logging.error(f"任务 {task_id} - 单据 {bill_no} - 所有批次处理完成，成功: {success_batches} 批，失败: {failed_batches} 批")
            if failed_batches > 0:
                return False
            else:
                # self.mark_bill_imported(bill_no, import_log)
                return True

    # 根据日期查询单据编号FBillNo,以列表格式输出，如["AR2509004100","AR2509004101","AR2509004102"]
    def select_OutReturnbillno_by_date(self,starttime,endtime,formid):
        #定义每页查询行数
        limit = 2000
        select_datas = []
        try:
            for i in itertools.count(0,limit):
                select_json = {
                    "FormId": formid,
                    "FieldKeys": "FBillNo",
                    "FilterString": [{"FieldName":"FDate","Compare":"39","Value":starttime,"Left":"","Right":"","Logic":0},{"FieldName":"FDate","Compare":"16","Value":endtime,"Left":"","Right":"","Logic":"0"}],
                    "OrderString": "",
                    "TopRowCount": 0,
                    "StartRow": i,
                    "Limit": limit,
                    "SubSystemId": ""
                }
                response = self.sdk.ExecuteBillQuery(select_json)
                result = ast.literal_eval(response)
                if len(result) > 0:
                    result_list = [item[0] for item in result]
                    select_datas = select_datas+result_list
                else:
                    break
            logging.info(f"成功查询{formid}金蝶表：日期区间：{starttime} ~ {endtime}的FBillNo，共{len(select_datas)}个")
            return select_datas
        except Exception as e:
            logging.error(f"查询{formid}FBillNO失败:{e}，response返回结果为：{response}")
            return select_datas

    # 根据【销售出库/销售退货】的单据编号查询【应收单】的单据编号（FBillNo），用【源单编号：FSourceBillNo】筛选，
    # 传入str:bill_data，如："AR2509004100,AR2509004101,AR2509004102,...",输出列表，输出结果需要去重
    def select_Receivebillno_by_billno(self,bill_data,formid,bill_data_type):
        #定义每页查询行数limit，由于应收单查出来的FBillNo会重复，所以这里设置可以大一点
        limit = 10000
        select_datas = []
        try:
            # 若传入的是list需转为str,map确保所有元素都是字符串
            if isinstance(bill_data, list):
                bill_data = ','.join(map(str,bill_data))
            for i in itertools.count(0,limit):
                select_json = {
                    "FormId": formid,
                    "FieldKeys": "FBillNo",
                    "FilterString":[{"FieldName":"FSourceBillNo","Compare":"67","Value":bill_data,"Left":"","Right":"","Logic":"0"}],
                    "OrderString": "",
                    "TopRowCount": 0,
                    "StartRow": i,
                    "Limit": limit,
                    "SubSystemId": ""
                }
                response = self.sdk.ExecuteBillQuery(select_json)
                result = ast.literal_eval(response)
                if len(result) > 0:
                    result_list = [item[0] for item in result]
                    select_datas = select_datas + result_list
                else:
                    break
            # 去重
            unique_list = list(dict.fromkeys(select_datas))
            logging.info(f"成功根据{bill_data_type}的FBillNo查询金蝶表：{formid}的FBillNo，共{len(unique_list)}个，行数{len(select_datas)}")
            return unique_list
        except Exception as e:
            logging.error(f"查询{formid}FBillNO失败:{e}，response返回结果为：{response}")
            return select_datas

    # 根据【销售出库列表】的单据编号查询【销售退货列表】的单据编号（FBillNo），用【源单编号：FSrcBillNo】筛选
    # 传入str:bill_data，如："AR2509004100,AR2509004101,AR2509004102,...",输出列表，输出结果需要去重
    def select_SallReturnbillno_by_billno(self,bill_data,formid,bill_data_type):
        #定义每页查询行数limit，由于销售退货列表查出来的FBillNo会重复，所以这里设置可以大一点
        limit = 10000
        select_datas = []
        try:
            # 若传入的是list需转为str,map确保所有元素都是字符串
            if isinstance(bill_data, list):
                bill_data = ','.join(map(str, bill_data))
            for i in itertools.count(0,limit):
                select_json = {
                    "FormId": formid,
                    "FieldKeys": "FBillNo",
                    "FilterString":[{"FieldName":"FSrcBillNo","Compare":"67","Value":bill_data,"Left":"","Right":"","Logic":"0"}],
                    "OrderString": "",
                    "TopRowCount": 0,
                    "StartRow": i,
                    "Limit": limit,
                    "SubSystemId": ""
                }
                response = self.sdk.ExecuteBillQuery(select_json)
                result = ast.literal_eval(response)
                if len(result) > 0:
                    result_list = [item[0] for item in result]
                    select_datas = select_datas + result_list
                else:
                    break
            # 去重
            unique_list = list(dict.fromkeys(select_datas))
            logging.info(f"成功根据{bill_data_type}的FBillNo查询金蝶表：{formid}的FBillNo，共{len(unique_list)}个，行数{len(select_datas)}")
            return unique_list
        except Exception as e:
            logging.error(f"查询{formid}FBillNO失败:{e}，response返回结果为：{response}")
            return select_datas

    # 根据【销售出库列表/销售退货列表/应收单】的单据编号查询【业务凭证】的编码：FBillNo和对应总账凭证id：FGLVoucherID,用【来源单据号：FSourceBillNo】筛选
    # 传入str:bill_data，如："AR2509004100,AR2509004101,AR2509004102,...",输出列表
    def select_Voucher_by_billno(self,bill_data,formid,bill_data_type):
        #定义每页查询行数limit
        limit = 2000
        select_billno_datas = []
        select_FGLVoucherID_datas = []
        try:
            # 若传入的是list需转为str,map确保所有元素都是字符串
            if isinstance(bill_data, list):
                bill_data = ','.join(map(str, bill_data))
            for i in itertools.count(0,limit):
                select_json = {
                    "FormId": formid,
                    "FieldKeys": "FBillNo,FGLVoucherID",
                    "FilterString":[{"FieldName":"FSourceBillNo","Compare":"338","Value": bill_data,"Left":"","Right":"","Logic":0}],
                    "OrderString": "",
                    "TopRowCount": 0,
                    "StartRow": i,
                    "Limit": limit,
                    "SubSystemId": ""
                }
                response = self.sdk.ExecuteBillQuery(select_json)
                result = ast.literal_eval(response)
                if len(result) >0:
                    result_list_billno = [item[0] for item in result]
                    # 去除查询到的FVoucherGroupNo是0的数据，0代表没有
                    result_list_FGLVoucherID = [item[1] for item in result if item[1] != 0]
                    select_billno_datas = select_billno_datas + result_list_billno
                    select_FGLVoucherID_datas = select_FGLVoucherID_datas + result_list_FGLVoucherID
                else:
                    break
            logging.info(f"成功根据{bill_data_type}的FBillNo查询金蝶表：{formid}的FBillNo，共{len(select_billno_datas)}个")
            logging.info(f"成功根据{bill_data_type}的FBillNo查询金蝶表：{formid}的FGLVoucherID，共{len(select_FGLVoucherID_datas)}个")
            return select_billno_datas,select_FGLVoucherID_datas
        except Exception as e:
            logging.error(f"查询{formid}FBillNO失败:{e}，response返回结果为：{response}")
            return select_billno_datas, select_FGLVoucherID_datas

    # 各报表反审核处理，
    # 用ids反审，字符串类型如："AR2509004100,AR2509004101,AR2509004102,..."
    # 用Numbers反审，数组类型如：[]
    def kingdeeAPI_UnAudit(self,formid,data):
        logging.info(f"开始执行删除前反审核")
        try:
            if formid == "GL_VOUCHER":
                unaudit_data = {
                    "CreateOrgId": 0,
                    "Numbers": [],
                    "Ids": data,
                    "InterationFlags": "",
                    "IgnoreInterationFlag": "",
                    "UseOrgId": 0,
                    "NetworkCtrl": "",
                    "IsVerifyProcInst": "true"
                }
            else:
                unaudit_data = {
                    "CreateOrgId": 0,
                    "Numbers": data,
                    "Ids": "",
                    "InterationFlags": "",
                    "IgnoreInterationFlag": "",
                    "UseOrgId": 0,
                    "NetworkCtrl": "",
                    "IsVerifyProcInst": "true"
                }
            response = self.sdk.UnAudit(formid, unaudit_data)

            # 获取反审核结果
            resp_json = json.loads(response)
            result = resp_json.get("Result", {})
            response_status = result.get("ResponseStatus", {})
            # 获取反审核失败的单据信息
            Errors = response_status.get("Errors", [])
            # 获反审核成功的单据信息
            SuccessEntitys = response_status.get("SuccessEntitys", [])

            logging.info(f"反审核结果返回 - 反审核失败的单据信息：{Errors}")
            logging.info(f"反审核结果返回 - 反审核成功的单据信息：{SuccessEntitys}")
        except Exception as e:
            logging.error(f"反审核未响应成功，错误反馈：{e}")

    #删除销售退货单列表/销售退货单列表，需要先反审核，这个传入list
    def delete_SAL_STOCK(self,formid,BillNo):
        if not BillNo:
            logging.warning(f"{formid} - 单据号为空，不执行删除")
            return False
        # 开始删除
        logging.info(f"{formid} - 开始执行删除 - 总共{len(BillNo)}个单据编号")
        # 反审核
        self.kingdeeAPI_UnAudit(formid,BillNo)
        try:
            delete_data = {
                "CreateOrgId": 0,
                "Numbers": BillNo,
                "Ids": "",
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            response = self.sdk.Delete(formid,delete_data)
            if not response:
                logging.warning(f"{formid} - 接口无返回")
                return False
        except Exception as e:
            logging.warning(f"{formid} - 执行API删除异常：{e}")
            return False

        # 获取返回结果信息,成功的单据编号要返回列表，用来去除，避免下次重复传入
        resp_json = json.loads(response)
        result = resp_json.get("Result", {})
        response_status = result.get("ResponseStatus", {})
        # 获取删除失败的单据信息
        Errors = response_status.get("Errors", [])
        # 获删除成功的单据信息
        SuccessEntitys = response_status.get("SuccessEntitys", [])
        # 获取整体状态，若有失败，则返回false
        is_success = response_status.get("IsSuccess", False)
        # 获取错误代码
        # msg_code = response_status.get("MsgCode", "0")

        #返回处理结果
        if is_success and len(SuccessEntitys) > 0:
            logging.info(f"{formid} - 全部删除成功 - 总共{len(BillNo)}个单据编号，成功{len(SuccessEntitys)}，失败{len(Errors)}")
            return True
        else:
            logging.info(f"{formid} - 删除失败 - 总共{len(BillNo)}个单据编号，成功{len(SuccessEntitys)}，失败{len(Errors)}")
            logging.info(f"{formid} - 失败单据信息{Errors}")
            return False

    # 删除销售退货单列表/销售退货单列表，需要先反审核
    def delete_SALSTOCK(self, formid, BillNo):
        # 传入的是str，delete_data里的Numbers和传入反审核的要list
        BillNo = [BillNo]
        if not BillNo:
            logging.warning(f"{formid} - 单据号为空，不执行删除")
            return False
        # 开始删除
        logging.info(f"{formid} - 开始执行删除 - 当前删除{BillNo}")
        # 反审核
        self.kingdeeAPI_UnAudit(formid, BillNo)
        try:
            delete_data = {
                "CreateOrgId": 0,
                "Numbers": BillNo,
                "Ids": "",
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            response = self.sdk.Delete(formid, delete_data)
            if not response:
                logging.warning(f"{formid} - 接口无返回")
                return False
        except Exception as e:
            logging.warning(f"{formid} - 执行API删除异常：{e}")
            return False

        # 获取返回结果信息
        resp_json = json.loads(response)
        result = resp_json.get("Result", {})
        response_status = result.get("ResponseStatus", {})
        # 获取删除失败的单据信息
        Errors = response_status.get("Errors", [])
        # 获删除成功的单据信息
        SuccessEntitys = response_status.get("SuccessEntitys", [])
        # 获取整体状态，若有失败，则返回false
        is_success = response_status.get("IsSuccess", False)
        # 获取错误代码
        # msg_code = response_status.get("MsgCode", "0")

        if is_success:
            logging.info(f"{formid} - 删除成功：{SuccessEntitys}")
            return True
        else:
            logging.info(f"{formid} - 删除失败：{Errors}")
            return False

    # 删除应收单列表，需要先反审核，返回有False（没有数据被删除）、True（有数据被删除）
    def delete_AR_receivable(self, formid, BillNo):
        if not BillNo:
            logging.warning("应收单列表 - 单据号为空，不执行删除")
            return False
        # 开始删除
        logging.info(f"应收单列表 - 开始执行删除 - 总共{len(BillNo)}个单据编号")
        # 反审核(默认反审核的结果如何都进行删除。一般来说都能反审核成功，不成功也是因为不用反审核，不会出现是因为已经关账的原因（如果真关账了可以去调关账日期区间）)
        self.kingdeeAPI_UnAudit(formid, BillNo)
        try:
            delete_data = {
                "CreateOrgId": 0,
                "Numbers": BillNo,
                "Ids": "",
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            response = self.sdk.Delete(formid, delete_data)
            if not response:
                logging.warning(f"应收单列表 - 接口无返回")
                return False
        except Exception as e:
            logging.warning(f"应收单列表 - 执行API删除异常：{e}")
            return False

        # 获取返回结果信息
        resp_json = json.loads(response)
        result = resp_json.get("Result", {})
        response_status = result.get("ResponseStatus", {})
        # 获取删除失败的单据信息
        Errors = response_status.get("Errors", [])
        # 获删除成功的单据信息
        SuccessEntitys = response_status.get("SuccessEntitys", [])
        # 获取整体状态，若有失败，则返回false
        is_success = response_status.get("IsSuccess", False)
        # 获取错误代码
        # msg_code = response_status.get("MsgCode", "0")

        if is_success and len(SuccessEntitys) > 0:
            logging.info(f"应收单列表 - 全部删除成功 - 总共{len(BillNo)}个单据编号 - 成功{len(SuccessEntitys)}，失败{len(Errors)}")
            return True
        else:
            logging.info(f"应收单列表 - 删除失败 - 总共{len(BillNo)}个单据编号 - 成功{len(SuccessEntitys)}，失败{len(Errors)}")
            logging.info(f"应收单列表 - 失败单据信息{Errors}")
            return False

    # 删除业务凭证，无反审核，返回有False、True
    def delete_BAS_BusinessVoucher(self, formid, BillNo):
        if not BillNo:
            logging.warning("业务凭证 - 单据号为空，不执行删除")
            return False
        # 开始删除
        logging.info(f"业务凭证 - 开始执行删除 - 总共{len(BillNo)}个单据编号")
        try:
            delete_data = {
                "CreateOrgId": 0,
                "Numbers": BillNo,
                "Ids": "",
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            response = self.sdk.Delete(formid, delete_data)
            if not response:
                logging.warning(f"业务凭证 - 接口无返回")
                return False
        except Exception as e:
            logging.warning(f"业务凭证 - 执行API删除异常：{e}")
            return False

        # 获取返回结果信息
        resp_json = json.loads(response)
        result = resp_json.get("Result", {})
        response_status = result.get("ResponseStatus", {})
        # 获取删除失败的单据信息
        Errors = response_status.get("Errors", [])
        # 获删除成功的单据信息
        SuccessEntitys = response_status.get("SuccessEntitys", [])
        # 获取整体状态，若有失败，则返回false
        is_success = response_status.get("IsSuccess", False)
        # 获取错误代码
        # msg_code = response_status.get("MsgCode", "0")

        # 返回处理结果
        if is_success and len(SuccessEntitys) > 0:
            logging.info(f"业务凭证 - 全部删除成功 - 总共{len(BillNo)}个单据编号，成功{len(SuccessEntitys)}，失败{len(Errors)}")
            return True
        else:
            logging.info(f"业务凭证 - 删除失败 - 总共{len(BillNo)}个单据编号，成功{len(SuccessEntitys)}，失败{len(Errors)}")
            logging.info(f"业务凭证 - 失败单据信息{Errors}")
            return False

    # 用ids删除总账凭证，需要先反审核，返回有False、True
    def delete_GL_VOUCHER(self, formid, fid):
        if not fid:
            logging.warning("总账凭证 - 单据号为空，不执行删除")
            return False
        # 开始删除
        logging.info(f"总账凭证 - 开始执行删除 - 总共{len(fid)}个凭证编号")
        # 若传入的是list需转为str,map确保所有元素都是字符串
        if isinstance(fid, list):
            fids = ','.join(map(str, fid))

        # 反审核
        self.kingdeeAPI_UnAudit(formid, fids)
        try:
            delete_data = {
                "CreateOrgId": 0,
                "Numbers": [],
                "Ids": fids,
                "NetworkCtrl": True  # 启用网控，减少并发冲突
            }
            response = self.sdk.Delete(formid, delete_data)
            if not response:
                logging.warning(f"总账凭证 - 接口无返回")
                return False
        except Exception as e:
            logging.warning(f"总账凭证 - 执行API删除异常：{e}")
            return False

        # 获取返回结果信息
        resp_json = json.loads(response)
        result = resp_json.get("Result", {})
        response_status = result.get("ResponseStatus", {})
        # 获取删除失败的单据信息
        Errors = response_status.get("Errors", [])
        # 获删除成功的单据信息
        SuccessEntitys = response_status.get("SuccessEntitys", [])
        # 获取整体状态，若有失败，则返回false
        is_success = response_status.get("IsSuccess", False)
        # 获取错误代码
        # msg_code = response_status.get("MsgCode", "0")

        # 返回处理结果
        if is_success and len(SuccessEntitys) > 0:
            logging.info(f"总账凭证 - 全部删除成功 - 总共{len(fid)}个凭证编号：{fids}")
            return True
        else:
            logging.info(f"总账凭证 - 删除失败 - 总共{len(fid)}个凭证编号 - 成功：{SuccessEntitys}，失败：{Errors}")
            return False

    # 周期性执行任务检查和处理
    def start_periodic_task(self, interval=5):
        logging.info(f"启动周期性任务调度（间隔{interval}分钟）")

        # 定义定时执行的任务函数
        def job():
            try:
                # 1. 检查是否有正在处理的任务（run_flag=21）
                has_running_tasks = self._check_running_tasks()
                if has_running_tasks:
                    logging.info("检测到有任务处于同步中（run_flag=21），跳过本次查询")
                    return  # 直接返回，等待下一次调度

                # 2. 无正在处理的任务，执行正常的任务处理流程
                logging.info("无同步中任务，开始查询待执行任务（run_flag=20）")
                self.run()  # 调用已有的run方法处理任务

            except Exception as e:
                logging.error(f"周期性任务执行异常: {str(e)}", exc_info=True)

        # 设置定时任务（每interval分钟执行一次）
        schedule.every(interval).minutes.do(job)
        # 启动后手动执行一次job，实现“立即执行第一次任务”
        logging.info("程序启动，立即执行第一次任务查询...")
        job()  # 手动触发第一次执行

        # 启动调度循环（持续检查并执行到期的定时任务）
        while True:
            schedule.run_pending()  # 运行所有等待执行的任务
            time.sleep(1)  # 休眠1秒，降低CPU占用

    # 检查是否存在run_flag=21的任务
    def _check_running_tasks(self):
        conn = None
        cursor = None
        try:
            conn = pymysql.connect(
                host=self.config.get('db.host'),
                port=int(self.config.get('db.port', '3306')),
                user=self.config.get('db.user'),
                password=self.config.get('db.password'),
                db='lqetl',
                charset='utf8mb4'
            )
            cursor = conn.cursor()
            # 查询是否有run_flag=21的任务
            cursor.execute("SELECT COUNT(*) FROM kd_task WHERE run_flag = 21")
            count = cursor.fetchone()[0]
            return count > 0  # 存在则返回True
        except Exception as e:
            logging.error(f"检查同步中任务失败: {str(e)}")
            return False  # 异常时默认视为有任务在运行，避免重复处理
        finally:
            if cursor:
                cursor.close()
            if conn:
                conn.close()


# 启动入口
if __name__ == "__main__":
    try:
        importer = JinDieSDKImport(config_file='config.properties')
        importer.start_periodic_task(interval=5)  # 每5分钟执行一次
    except Exception as e:
        logging.error(f"程序启动失败: {str(e)}")
        sys.exit(1)